后悔了怎么办 —— undo 日志
0. 引言
redo 日志保证"提交了不丢",undo 日志则保证"没提交能悔棋"。undo 记录的是变更前的数据,它在事务回滚时反向恢复数据(原子性),也是 MVCC 多版本读的数据来源(隔离性)。本文解析 undo 的格式、版本链、purge 回收机制,以及 8.0 中 undo 表空间的管理。
1. undo 的作用
| 作用 | 机制 | 对应 ACID |
|---|---|---|
| 回滚 | 事务失败/ROLLBACK 时,按 undo 反向执行(INSERT→DELETE、UPDATE→旧值、DELETE→重新插入) | 原子性 |
| MVCC 多版本读 | 快照读时沿 undo 版本链回溯,读到符合 ReadView 的旧版本 | 隔离性 |
图表渲染中…
redo 与 undo 的关键区别:redo 记录"变更后的值"用于前滚(崩溃恢复),undo 记录"变更前的值"用于回滚与多版本读;两者都先于数据页落盘(WAL)。
2. undo 日志的类型与格式
2.1 两类基础 undo
| 类型 | 记录内容 | 回滚动作 |
|---|---|---|
| insert undo | 插入行的主键(供回滚时精确定位删除);事务提交后立即可以被 purge(该行不会再被其他事务读到) | DELETE 该行 |
| update undo | 变更前的完整旧值(或旧值 + 主键),供回滚与 MVCC;提交后不能立即 purge(可能还有事务快照引用) | 恢复旧值 / 重新插入 |
2.2 记录格式核心字段
text
undo 记录(简化):
- trx_id:修改该行的事务 ID(MVCC 可见性判断的关键)
- roll_pointer:指向上一个版本的 undo 记录(版本链指针)
- 变更前数据(旧值)2.3 版本链
同一行的多次修改形成一条版本链——最新版在聚簇索引记录中,历史版本通过 roll_pointer 串在 undo 日志上:
text
聚簇索引记录(最新,trx_id=30)
└─ roll_pointer → undo 版本3(trx_id=26)
└─ roll_pointer → undo 版本2(trx_id=21)
└─ roll_pointer → undo 版本1(trx_id=15,初始 INSERT)快照读时,事务从最新版本出发,沿版本链回溯,找到第一个对自身可见的版本(判断逻辑见《深入理解事务与锁机制》的 ReadView 部分)。
3. purge:undo 的回收
- purge 线程(
innodb_purge_threads,8.0 默认 4 个)负责两件事:- 回收已提交且不再被任何事务快照引用的 undo 页;
- 顺带完成"删除标记行"的物理清理(
DELETE只是打删除标记,真正空间回收靠 purge)。
- 判断"不再被引用":全局最小活跃 ReadView(
view_low_limit_id)之后产生的 undo 都安全; - 8.0 中 undo 页也支持压缩(
innodb_undo_log_compress,默认开启)。
4. undo 表空间管理(8.0)
5.7 起 undo 从系统表空间 ibdata1 中分离;8.0 完全独立管理:
| 配置 | 默认值 | 说明 |
|---|---|---|
innodb_undo_tablespaces | 2(初始 undo_001/002) | undo 表空间数量(8.0 中该参数只影响初始创建,后续用 SQL 动态增删) |
innodb_undo_directory | 数据目录 | undo 表空间存放路径(可放独立磁盘) |
innodb_max_undo_log_size | 1GB | undo 表空间大小阈值,超过后被标记截断 |
innodb_undo_log_truncate | ON | 超过阈值自动截断回收空间(需 ≥2 个 undo 表空间轮换) |
innodb_purge_rseg_truncate_frequency | 128 | 截断检查频率 |
sql
-- 8.0 动态创建/删除 undo 表空间
CREATE UNDO TABLESPACE undo_003 ADD DATAFILE 'undo_003.ibu';
DROP UNDO TABLESPACE undo_003;监控:
sql
-- 查看 undo 表空间状态
SELECT * FROM information_schema.INNODB_TABLESPACES WHERE SPACE_TYPE='Undo'\G
-- 查看 undo 日志使用情况
SHOW ENGINE INNODB STATUS\G -- 关注 History list length(未 purge 的 undo 数量)History list length 过大:说明有长事务长期持有快照,undo 无法 purge,导致 undo 表空间暴涨、版本链过长拖慢查询。这是"长事务是万恶之源"的又一佐证。
5. 常见问题
- 长事务导致 undo 爆炸:事务迟迟不提交 → 快照一直有效 → undo 无法回收。解法:事务短小、应用层避免事务内做 IO/远程调用;
- undo 空间不足(
undo tablespace full):调大innodb_max_undo_log_size或增加 undo 表空间; - undo 与 redo 混淆:面试高频——redo 前滚(重做)、undo 回滚(撤销);redo 记录变更后值、undo 记录变更前值;
- MVCC 依赖 undo:没有 undo 就没有多版本,也就没有非阻塞读——这是 InnoDB 高并发的根基。
6. 小结
- undo 一物两用:回滚(原子性)+ 版本链(MVCC 隔离性);
- insert undo 提交即可 purge,update undo 需等无事务引用;
- 8.0 undo 独立表空间 + 自动截断,一切可动态管理;
- 警惕长事务:它同时拖垮 undo 回收、版本链查询与 binlog 积压。
下一章讲解工作面试老大难 —— InnoDB 锁:锁类型、兼容矩阵与加锁流程。